package com.hyou.springboot.aop;

import java.util.ArrayList;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.hyou.springboot.annotation.FormValidator;
import com.hyou.springboot.base.BaseForm;
import com.hyou.springboot.base.BaseVO;
import com.hyou.springboot.base.ErrorFiledVO;
import com.hyou.springboot.util.ResponseMessageUtil;

/**
 * <pre>
 * 表单数据校验处理
 * 如果表单数据校验结果通过，则继续调用Controller的业务方法。
 * 如果表单数据校验结果未通过，则返回错误应答。
 * 
 * 表单校验使用示例：
 * 1) 声明Form对象:
 * 
 * public class YourForm extends BaseForm {
 *     
 *     &#64;NotEmpty(message = "field不能为空")
 *     private String field;
 *     
 *     // getter. setter.
 *     
 * }
 * 
 * 2) 在Controller方法中使用：
 * public class YourController {
 * 
 *     &#64;FormValidator
 *     &#64;PostMapping(path = "/uri")
 *     public ResponseMessage method(@RequestBody YourForm form) {
 *         
 *         // do something
 *         // 不再使用默认的校验框架的校验方式，改为在AOP中手工调用表单校验API。
 *         // 这样在Controller方法中就可以简化掉ResultBinding参数
 *         
 *     }
 * 
 * }
 * 
 * </pre>
 * 
 * @author FengChangshuo
 * @version 1.0.0 2018-03-28 11:33:24 初始创建
 */
@Aspect
@Component("formValidatorAspect")
public class FormValidatorAspect {
    
    private static final Logger log = LoggerFactory.getLogger(FormValidatorAspect.class);
    
    private Validator validator;
    
    public FormValidatorAspect() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }
    
    @Pointcut("@annotation(com.hyou.springboot.annotation.FormValidator)")
    public void validatorPoint() {
        // 切入点标记
    }
    
    @Around("validatorPoint()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        
        /*
         * 1. 从方法的参数列表中获取Form表单对象。
         * 要求：所有的Form表单对象都要继承BaseForm类
         */
        Object[] methodArgs = joinPoint.getArgs();
        BaseForm form = null;
        
        for (Object arg : methodArgs) {
            if (arg instanceof BaseForm) {
                form = (BaseForm) arg;
                break;
            }
        }
        
        if (form == null) {
            return joinPoint.proceed();
        }
        
        /*
         * 2. Form表单校验
         */
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        FormValidator formValidator = methodSignature.getMethod().getAnnotation(FormValidator.class);
        
        Set<ConstraintViolation<BaseForm>> constraintViolationSet = validator.validate(form, formValidator.value());
        
        if (!constraintViolationSet.isEmpty()) {
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            log.debug("controller : {}", className);
            log.debug("method : {}", methodName);
            
            ArrayList<ErrorFiledVO> errs = new ArrayList<>();
            for (ConstraintViolation<BaseForm> item : constraintViolationSet) {
                String formClassName = item.getRootBeanClass().getName();
                String fieldName = PathImpl.class.cast(item.getPropertyPath()).getLeafNode().getName();
                String errorMessage = item.getMessage();
                Object invalidValue = item.getInvalidValue();
                
                log.debug("formClassName : {}", formClassName);
                log.debug("fieldName : {}", fieldName);
                log.debug("message : {}", errorMessage);
                log.debug("invalidValue : {}", invalidValue);
                
                ErrorFiledVO errFieldVO = new ErrorFiledVO();
                errFieldVO.setErrField(fieldName);
                errFieldVO.setErrMsg(errorMessage);
                errs.add(errFieldVO);
            }
            
            BaseVO baseVo = new BaseVO();
            baseVo.setErrField(errs);
            return ResponseMessageUtil.genErrorDataValidateResult(baseVo);
        }
        
        return joinPoint.proceed();
    }
    
}
